/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 * 	  David Corbin  - added Revert action
 ******************************************************************************/
package net.sourceforge.perforce.ui.views;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.sourceforge.perforce.core.IResourceStateChangeListener;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.ResourceStateChangeAdapter;
import net.sourceforge.perforce.core.api.IOpenedResult;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.api.IWhereResult;
import net.sourceforge.perforce.core.internal.P4TeamProvider;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.resources.LocalFile;
import net.sourceforge.perforce.core.resources.LocalResource;
import net.sourceforge.perforce.core.syncinfo.IResourceStateDelta;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;
import net.sourceforge.perforce.ui.IPerforceUIConstants;
import net.sourceforge.perforce.ui.PerforceUIPlugin;
import net.sourceforge.perforce.ui.dialogs.ChangeSpecDialog;
import net.sourceforge.perforce.ui.internal.Policy;
import net.sourceforge.perforce.ui.internal.model.ChangelistElement;
import net.sourceforge.perforce.ui.internal.model.ClientPendingChangelistContainer;
import net.sourceforge.perforce.ui.internal.model.DefaultChangelistElement;
import net.sourceforge.perforce.ui.internal.model.OtherPendingChangelistContainer;
import net.sourceforge.perforce.ui.model.AllRootsElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.ViewPart;

/**
  * The PendingChangelistView defines a view for managing Perforce changelists.
  * Compare with P4Win.
  * 
  * @version $Revision: 67 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  * @author  <a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
  */
public class PendingChangelistView extends ViewPart
{
	// ---------------------------------------------------------------------------- Public Constants
	
	/** The view's identifier. */
	public static final String ID = 
		PerforceUIPlugin.ID + ".views.PendingChangelistView";                          //$NON-NLS-1$	
	
	// -------------------------------------------------------------------- Private Static Variables
	
	/** Stores the view's singleton instance. */
	private static PendingChangelistView instance;
	
	// --------------------------------------------------------------------------- Private Variables

	private AllRootsElement root;
	private TreeViewer viewer;	
	private DrillDownAdapter drillPart;
	private RefreshAction refreshAction;
	private RevertAction revertAction;
	
	/** Resource State Change Listener. */
	private IResourceStateChangeListener changeListener;

	// ----------------------------------------------------------------------- Public Static Methods
	
	/** Call this method to retrieve the PendingChangeListView singleton.
	  *
	  * @return the singleton. 
	  */
	public static final PendingChangelistView getInstance()
	{
		return instance;
	}
	
	// ------------------------------------------------------------------------- Public Constructors
	
	/** Constructor for PendingChangelistView. */
	public PendingChangelistView()
	{
		instance = this;
	}
	
	// ------------------------------------------------------------------------------ Public Methods
	
	/** @see IWorkbenchPart#createPartControl(Composite) */
	public void createPartControl(Composite parent)
	{
		root = new AllRootsElement();
		
		viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		viewer.setContentProvider(new ContentProvider());	
		viewer.setLabelProvider(new WorkbenchLabelProvider());
		viewer.setSorter(new InternalSorter());
		viewer.setInput(root);		
		viewer.getControl().addKeyListener(new KeyAdapter() 
		{
			public void keyPressed(KeyEvent event) 
			{
				if (event.keyCode == SWT.F5)
				{
					refreshAction.run();
				}
			}
		});
			
		drillPart = new DrillDownAdapter(viewer);
									
		getSite().setSelectionProvider(viewer);			
		contributeActions();
		initDragAndDrop();		
		
		changeListener = new ResourceStateChangeAdapter() 
		{
			/** @see ResourceStateChangeAdapter#resourceStateChanged(IResource) */
			public void resourceStateChanged(IResourceStateDelta[] resourceStateDeltas) 
				throws PerforceException
			{
				Map changelists = new HashMap();
				for (int i = 0; i < resourceStateDeltas.length; i++)
				{
					IResource resource = resourceStateDeltas[i].getResource();
					
					ResourceSyncInfo oldSyncInfo = resourceStateDeltas[i].getOldResourceSyncInfo();					
					IChangelist oldChangelist = getChangelistForUpdate(resource, oldSyncInfo);
					if (oldChangelist != null)
					{
						changelists.put(oldChangelist, oldChangelist);
					}

					ResourceSyncInfo newSyncInfo = resourceStateDeltas[i].getNewResourceSyncInfo();
					IChangelist newChangelist = getChangelistForUpdate(resource, newSyncInfo);
					if (newChangelist != null)
					{
						changelists.put(newChangelist, newChangelist);
					}					
				}
				
				for (Iterator it = changelists.values().iterator(); it.hasNext();)
				{
					viewer.refresh(it.next());
				}
			}

			public IChangelist getChangelistForUpdate(IResource resource, ResourceSyncInfo syncInfo)
				throws PerforceException
			{
				if (syncInfo != null)
				{
					IPerforceServerManager serverManager = PerforceCorePlugin.getServerManager();
					IPerforceServer server = serverManager.getPerforceServer(resource);	
					
					Integer change = syncInfo.getChange();
					if (change != null)
					{
						if (change.equals(IChangelist.DEFAULT))
						{
							return new DefaultChangelistElement(server);
						}
						else
						{
							return new ChangelistElement(server, change);
						}
					}
				}
								
				return null;
			}

			public void changelistStateChanged(IChangelist[] changelists) 
			{
				Map servers = new HashMap();
				
				for (int i = 0; i < changelists.length; i++)
				{
					viewer.refresh(changelists[i]);
					if (!changelists[i].getId().equals(IChangelist.DEFAULT))
					{
						servers.put(changelists[i].getServer(), changelists[i].getServer());
					}
				}
				
				for (Iterator it = servers.values().iterator(); it.hasNext();)
				{
					IPerforceServer server = (IPerforceServer) it.next();					
					IChangelist defaultChangelist = new DefaultChangelistElement(server);
					viewer.refresh(defaultChangelist); 
				}
			}
			
			public void changelistStateChanged(IPerforceServer[] servers) 
			{
				for (int i = 0; i < servers.length; i++)
				{
					viewer.refresh(servers[i]);
				}
			}
		};
		PerforceCorePlugin.getPlugin().addResourceStateChangeListener(changeListener);
	}

	/** @see IWorkbenchPart#setFocus() */
	public void setFocus()
	{
		viewer.getControl().setFocus();
	}
	
	/** @see org.eclipse.ui.IWorkbenchPart#dispose() */
	public void dispose() 
	{
		PerforceCorePlugin.getPlugin().removeResourceStateChangeListener(changeListener);
		super.dispose();
	}

	
	// --------------------------------------------------------- Private Methods
	
	/** Adds drag and drop support to the pending changelists view. */
	private void initDragAndDrop() 
	{
//		int operations = DND.DROP_MOVE;
//		Transfer[] transfers = new Transfer[]
//		{ 
//			OpenedResultTransfer.getInstance() 
//		};
//		
//		PendingChangelistsDragAdapter dragAdapter = 
//			new PendingChangelistsDragAdapter(viewer);		
//		viewer.addDragSupport(operations, transfers, dragAdapter);
//		
//		PendingChangelistsDropAdapter dropAdapter = 
//			new PendingChangelistsDropAdapter(viewer);
//		dropAdapter.setFeedbackEnabled(false);
//		viewer.addDropSupport(operations, transfers, dropAdapter);
	}	

	/** 
	  * Contribute actions to the view. 
	  */
	private void contributeActions() 
	{
		viewer.addDoubleClickListener(new DoubleClickListener());
		
		refreshAction = new RefreshAction();		
		revertAction = new RevertAction();		

		// Create the popup menu
		MenuManager menuMgr = new MenuManager();
		Tree tree = viewer.getTree();
		Menu menu = menuMgr.createContextMenu(tree);
		menuMgr.addMenuListener(new IMenuListener() 
		{
			public void menuAboutToShow(IMenuManager manager) 
			{
				manager.add(new Separator(IWorkbenchActionConstants.SEP));		

				manager.add(
					new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
									
				manager.add(new Separator(IWorkbenchActionConstants.SEP));					
				manager.add(refreshAction);	
				
				IStructuredSelection sel = (IStructuredSelection)viewer.getSelection();
				for (Iterator it = sel.iterator(); it.hasNext();)
				{
					Object element = it.next();
					if (element instanceof LocalFile)
					{
						LocalFile local = (LocalFile) element;
						
						if (local.isCheckedOut())
						{
							manager.add(new Separator(IWorkbenchActionConstants.SEP));
							manager.add(revertAction);			
							break;
						}
					}
				}
			}
		});
		menuMgr.setRemoveAllWhenShown(true);
		tree.setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	
		// Create the local tool bar
		IActionBars bars = getViewSite().getActionBars();
		IToolBarManager tbm = bars.getToolBarManager();
		drillPart.addNavigationActions(tbm);
		tbm.add(refreshAction);
		tbm.update(false);	
	}

	// ----------------------------------------------------------------------- Private Inner Classes
	
	/** Handler class for double clicks.
	  *
	  * TODO: (re-) add double click of LocalFile. 
	  */
	private class DoubleClickListener implements IDoubleClickListener
	{
		/** @see IDoubleClickListener#doubleClick(DoubleClickEvent) */
		public void doubleClick(DoubleClickEvent event) 
		{
			IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
			IAdaptable element = (IAdaptable) selection.getFirstElement();
			
			if (element instanceof IChangelist)
			{
				doubleClickChangelist((IChangelist) element);
			}
			else
			{
				drillPart.goInto();
			}
		}

		/** Performs action that handles double clicks on IChangesResult
		  * elements.
		  * 
		  * @param changelist the changelist.
		  */
		private void doubleClickChangelist(IChangelist changelist) 
		{
			String clientname = changelist.getServer().getClient();
			
			if (changelist.getId().intValue() != -1)
			{
				try
				{
					Shell shell = getViewSite().getShell();
					ChangeSpecDialog dialog = ChangeSpecDialog.newInstance(shell, changelist);
					
					if (!(clientname.equals(changelist.getClientName())))
					{
						dialog.setReadOnly(true);
					}
						
					if (dialog.open() == ChangeSpecDialog.OK && !dialog.isReadOnly())
					{
						String changeSpec = dialog.getChangeSpecString();
						changelist.getServer().change(changeSpec);
						
						PerforceCorePlugin.getPlugin().broadcastChangelistStateChanges(
							new IChangelist[] { changelist });
					}
				}
				catch (PerforceException ex)
				{
					ErrorDialog.openError(
						null, 
						"Perforce Error", 
						"Failure.", 
						ex.getStatus());
				}
			}
		}	
	};

	/** Action class for refreshing the view. */
	private class RefreshAction extends Action
	{
		// ---------------------------------------------------------------------------- Constructors
		
		/** Constructor for RefreshAction. */
		RefreshAction()
		{
			super(Policy.bind("PendingChangelistView.refreshAction.label"));           //$NON-NLS-1$

			setImageDescriptor(
				PerforceUIPlugin.getPlugin().getImageDescriptor(IPerforceUIConstants.IMG_REFRESH));
		}
		
		// -------------------------------------------------------------------------- Public Methods
		
		/** @see Action#run() */
		public void run() 
		{
			viewer.refresh();
		}	
	};
	
	/** Action class for reverting selected files. */
	private class RevertAction extends Action
	{
		// ---------------------------------------------------------------------------- Constructors
		
		/** Constructor for RevertAction. */
		RevertAction()
		{
			super(Policy.bind("PendingChangelistView.revertAction.label"));            //$NON-NLS-1$			
		}
		
		/** @see Action#run() */
		public void run()
		{
			List localFiles = new LinkedList();
			try
			{	
				IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();
				for (Iterator it = sel.iterator(); it.hasNext();)
				{
					IAdaptable element = (IAdaptable) it.next();
					if (element instanceof LocalFile)
					{
						LocalFile local = (LocalFile) element;
						
						if (local.isCheckedOut())
						{
							localFiles.add(local);							
						}	
					}
				}
				
				List resources = new LinkedList();
				if (localFiles.size() > 0)
				{
					int i = 0;
					for (Iterator it = localFiles.iterator(); it.hasNext();)
					{
						LocalFile local = (LocalFile) it.next();
						local.revert();
						
						IResource resource = local.toResource();
						if (resource != null)
						{
							resources.add(resource);
						}
					}
				}
				
				/*
				PerforceCorePlugin.getPlugin().broadcastResourceStateChanges(
					(IResource[]) resources.toArray(new IResource[resources.size()]));
				*/
			}
			catch(PerforceException e)
			{
				ErrorDialog.openError(
					null, 
					"Perforce Error", 
					"A Perforce operation (p4 revert) failed.", 
					e.getStatus());
			}

		}		
	};
	
	/**
	  * This class makes the tree view a little "smarter" ;)
	  */
	private static class ContentProvider extends WorkbenchContentProvider
	{
		/** @see ITreeContentProvider#hasChildren(Object) */
		public boolean hasChildren(Object element)
		{
			if (element == null)
			{
				return false;
			}
				
			if (element instanceof LocalFile)
			{
				return false;
			}
			
			return true;			
		}
		
		/** @see org.eclipse.ui.model.WorkbenchContentProvider#getChildren(Object) */
		public Object[] getChildren(Object element) 
		{
			if (!(element instanceof IPerforceServer))
			{
				return super.getChildren(element);	
			}
			
			IPerforceServer server = (IPerforceServer) element;
			Object[] result = new Object[] 
			{ 
				new ClientPendingChangelistContainer(server), 
				new OtherPendingChangelistContainer(server),
			};
			return result;
		}
	}	

	/**
	  * Class responsible for sorting the tree.
	  */
	private static class InternalSorter extends ViewerSorter
	{
		/** @see ViewerSorter#compare(Viewer, Object, Object) */
		public int compare(Viewer viewer, Object e1, Object e2)
		{
			if (!(e1 instanceof IChangelist) || !(e2 instanceof IChangelist))
			{
				return super.compare(viewer, e1, e2);
			}
				
			IChangelist changelist1 = (IChangelist) e1;
			IChangelist changelist2 = (IChangelist) e2;			
				
			return changelist1.getId().intValue() - changelist2.getId().intValue();
		}
	}
}
